using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public class BinaryExpressionAdapter : BlockNodeFactory, IMultiplicityConvertible<CommonSyntaxNode>, IReferencable
    {
        BinaryExpressionSyntax _binaryExpression;
        IDocument _doc;
        BlockSyntax _block;
        StatementSyntax _stmt;
        IdentifierNameSyntax _identifier;

        #region constructor
        public BinaryExpressionAdapter(BinaryExpressionSyntax node)
        {
            _binaryExpression = node;
        }

        public BinaryExpressionAdapter(IDocument doc, BlockSyntax block, BinaryExpressionSyntax binaryExpression, StatementSyntax stmt, IdentifierNameSyntax identifer) : this(binaryExpression)
        {
            _doc = doc;
            _block = block;
            _stmt = stmt;
            _identifier = identifer;

        }

        #endregion

        #region Convert Binary Expression
        /// <summary>
        /// TODO: R-MembAcc1 ? Check if left side is option and if null????
        /// R-MembAcc2
        /// Converts the Binary Expression
        /// </summary>
        /// <param name="blockSyntax"></param>
        /// <param name="nodeToReplaceInBlock"></param>
        /// <returns></returns>
        public override StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName)
        {
            var stmtReplacement = new StatementReplacement<CommonSyntaxNode>();

            // var expressionStatement = (ExpressionStatementSyntax)nodeToReplaceInBlock.Statement;
            var binaryExpression = _binaryExpression as BinaryExpressionSyntax;
            if (!(binaryExpression.Left is IdentifierNameSyntax) || !(binaryExpression.Right is ExpressionSyntax))
                return null;

            if (binaryExpression.Kind == SyntaxKind.EqualsExpression)
                return ConvertEqualsExpression(newVariableName);

            //First check the leftside of the node for the multiplicity. If it is not Any, we can return
            var leftIdentifier = (IdentifierNameSyntax)binaryExpression.Left;
            var leftSideNode = SemanticModel.FindDecleration(leftIdentifier);
            var leftDecl = SyntaxElementFactory.GetDeclaration(leftSideNode,SemanticModel);

            if (leftDecl.MultiplicityType != AttributeType.Any) return null;

            //Now check the right side. Depending on the multiplicity of the right side we will choose a proper replacement
            ExpressionSyntax expression = (ExpressionSyntax)binaryExpression.Right;
            CommonSyntaxNode declerationNode = null;
            var multiplicityType = AttributeType.None;
            if (expression is IdentifierNameSyntax)
            { 
                declerationNode = SemanticModel.FindDecleration((IdentifierNameSyntax)expression);
                var decl = SyntaxElementFactory.GetDeclaration(declerationNode, SemanticModel);

                multiplicityType = decl.MultiplicityType;
            }
            //Now start with the transformation
            //In Case of add assign += we don't want to delete the list, but we want to add the item
            if (_binaryExpression.Kind == SyntaxKind.AddAssignExpression)
            {
                stmtReplacement = ConvertAddAssignExpression(multiplicityType, leftIdentifier, expression, newVariableName);
            }
            //Here we remove the item from the list
            else if (_binaryExpression.Kind == SyntaxKind.SubtractAssignExpression)
            {
                stmtReplacement = ConvertSubtractAssignExpression(multiplicityType, leftIdentifier, expression, newVariableName);
            }
            //and here we basically clear the list and then add the same code as in addassignexpression
            else
            {
                string tempVariable = VariableCreatorService.GetTempVariable();
                StatementSyntax clearStatement = Syntax.ParseStatement(string.Format("{0}.Clear();" + Environment.NewLine, newVariableName));
                stmtReplacement = ConvertAddAssignExpression(multiplicityType, leftIdentifier, expression, newVariableName);
                var additionalStmt = new List<StatementSyntax>() { clearStatement };
                additionalStmt.AddRange(stmtReplacement.AdditionalStatements);
                stmtReplacement.AdditionalStatements = additionalStmt;

            }

            return stmtReplacement;

        }

        /// <summary>
        /// Implements the rules for R-Ident1, R-Ident2, R-Ident3 and R-Ident4
        /// </summary>
        /// <param name="blockSyntax"></param>
        /// <param name="nodeToReplaceInBlock"></param>
        /// <returns></returns>
        private StatementReplacement<CommonSyntaxNode> ConvertEqualsExpression(string newVariableName)
        {

            var statementReplacements = new StatementReplacement<CommonSyntaxNode>();
            var binaryExpression = _binaryExpression as BinaryExpressionSyntax;
            var newExpressionIdentifier = Syntax.IdentifierName(newVariableName);
            //First check the leftside of the node for the multiplicity. If it is not Any, we can return
            var leftIdentifier = (IdentifierNameSyntax)binaryExpression.Left;
            var leftSideNode = SemanticModel.FindDecleration(leftIdentifier);
            var declaration = SyntaxElementFactory.GetDeclaration(leftSideNode, SemanticModel);
            
            
            
            var leftSideMultiplicity = declaration.MultiplicityType;

            //Secondly check the right side of the node for multiplicity
            var rightIdentifier = (IdentifierNameSyntax)binaryExpression.Right;
            var rightSideNode = SemanticModel.FindDecleration(rightIdentifier);
            var rightDecl = SyntaxElementFactory.GetDeclaration(rightSideNode, SemanticModel);
            var rightSideMultiplicity = rightDecl.MultiplicityType;

            var result = VariableCreatorService.GetTempVariable();
            var newIdentifier = Syntax.IdentifierName(result);
            var tempLeft = VariableCreatorService.GetTempVariable();
            var tempRight = VariableCreatorService.GetTempVariable();
            //R-Ident1
            if (leftSideMultiplicity == AttributeType.Option && rightSideMultiplicity == AttributeType.Option)
            {
                // statementsToAdd.Add(NodeToReplaceInBlock.Statement);
                //Change only the name of the identifier
                statementReplacements = new StatementReplacement<CommonSyntaxNode>(_binaryExpression, binaryExpression.WithLeft(newExpressionIdentifier));
                //StatementReplacements.Add(new StatementReplacement(NodeToReplaceInBlock.Statement, NodeToReplaceInBlock.Statement));
            }
            else
            {
                var assignTempLeft = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempLeft, newVariableName));
                var assignTempRight = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, tempRight, binaryExpression.Right));
                StatementSyntax assignResult;
                if (leftSideMultiplicity == AttributeType.Any && rightSideMultiplicity == AttributeType.Option)
                {
                    //@Text : IsEmpty does not exist, but we could use first with the same effect. Has also the advantage that we don't need to clone
                    //var assignResult = Syntax.ParseStatement(string.Format("{0}.Count == 0 ? {1} == null : {0}.Clone().Remove({1})" + Environment.NewLine,
                    //    tempLeft,tempRight));
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == 0 ? {2} == null : {1}.First() == {2};" + Environment.NewLine,
                        result, tempLeft, tempRight));
                }
                else if (leftSideMultiplicity == AttributeType.Option && rightSideMultiplicity == AttributeType.Any)
                {
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == 0 ? {2} == null : {1}.First() == {2};" + Environment.NewLine,
                        result, tempRight, tempLeft));
                }
                else
                {
                    //@Text : Here the Linq Method Except is used instead of cloning and deleting the list
                    assignResult = Syntax.ParseStatement(string.Format("var {0} = {1}.Count == {2}.Count && {1}.Except({2}).Count() == 0; " + Environment.NewLine,
                        result, tempLeft, tempRight));
                }

                statementReplacements = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { assignTempLeft, assignTempRight, assignResult } ,
                    _binaryExpression,
                     newIdentifier);

              
            }
            return statementReplacements;


        }

        /// <summary>
        /// Implements rule (M-SUB1)
        /// </summary>
        /// <param name="newBlock">the block where the statements are added</param>
        /// <param name="rightSideMultiplicity">multiplicity of the right side </param>
        /// <param name="leftIdentifier">identifier of the left side (the a)</param>
        /// <param name="rightExpression">Expression of the right side (the b or a constuctor call for instance)</param>
        /// <returns>list of statements which will be replaced</returns>
        private StatementReplacement<CommonSyntaxNode> ConvertSubtractAssignExpression(AttributeType rightSideMultiplicity,
            IdentifierNameSyntax leftIdentifier, ExpressionSyntax rightExpression, string newVariableName)
        {
            var statementReplacement = new StatementReplacement<CommonSyntaxNode>();
            string loopVariable = VariableCreatorService.GetTempVariable();
            if (rightSideMultiplicity == AttributeType.Any)
            {
                string resultVariable = VariableCreatorService.GetTempVariable();
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, newVariableName));
                var removeLoop = Syntax.ParseStatement(string.Format("foreach (var {0} in {1}) {2}.Remove({0});" + Environment.NewLine,
                    loopVariable, rightExpression.ToString(), resultVariable.ToString()));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
               
                statementReplacement = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { resultStatementInit, removeLoop },
                     _binaryExpression, expressionReplacement);
            }
            //Option, Bare te
            else
            {
                string newVariable = VariableCreatorService.GetTempVariable();
                string resultVariable = VariableCreatorService.GetTempVariable();
                StatementSyntax initializeStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, newVariable, rightExpression.ToString()));
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, newVariableName));
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Remove({0});" + Environment.NewLine,
                    newVariable, newVariableName.ToString()));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));

                statementReplacement = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { initializeStatement, resultStatementInit, conditionStatement },
                     _binaryExpression, expressionReplacement);



            }
            return statementReplacement;
        }

        /// <summary>
        /// (M-ADD1)
        /// Applys the rules to add a new element aka a += b
        /// </summary>
        /// <param name="newBlock">the block where the statements are added</param>
        /// <param name="rightSideMultiplicity">multiplicity of the right side </param>
        /// <param name="leftIdentifier">identifier of the left side (the a)</param>
        /// <param name="rightExpression">Expression of the right side (the b or a constuctor call for instance)</param>
        /// <returns>list of statements which will be replaced</returns>
        private StatementReplacement<CommonSyntaxNode> ConvertAddAssignExpression(AttributeType rightSideMultiplicity,
            IdentifierNameSyntax leftIdentifier, ExpressionSyntax rightExpression, string newVariableName)
        {
            var statementReplacements = new StatementReplacement<CommonSyntaxNode>();

            //If we are accessing an "any" field on the right side we want to addrange otherwie we just want to check the element and add it
            if (rightSideMultiplicity == AttributeType.Any)
            {
                var replacement = Syntax.ParseExpression(string.Format("{0}.AddRange({1})", newVariableName.ToString(), rightExpression.ToString()));
                statementReplacements = new StatementReplacement<CommonSyntaxNode>(_binaryExpression, replacement);
                //   StatementReplacements.Add(new StatementReplacement(_binaryExpression, replacement));

            }
            //Option, Bare te
            else
            {
                string newVariable = VariableCreatorService.GetTempVariable();
                string resultVariable = VariableCreatorService.GetTempVariable();
                //@Text:Because identifier might be a property which has site effects
                var initializeStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, newVariable, rightExpression.ToString()));
                var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, newVariableName));
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Add({0});" + Environment.NewLine,
                    newVariable, resultVariable));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
                //NodeToReplaceInBlock.Statement
                // var expressionReplacement = Syntax.ParseExpression(string.Format("{0} != null ? {1}.Add({0}) : {1};"));
               
                statementReplacements = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { initializeStatement, resultStatementInit, conditionStatement },
                _binaryExpression, expressionReplacement);
                //   StatementReplacements.Add(new StatementReplacement(_binaryExpression, expressionReplacement));

            }
            return statementReplacements;
        }

        public SyntaxNodeInfo GetReference()
        {
            SyntaxNodeInfo nodeInfo;
            if (_binaryExpression.Right == _identifier)
            {
                nodeInfo = new SyntaxNodeInfo(_doc, _block, _identifier, _stmt, _identifier);
            }
            else
            {
                nodeInfo = new SyntaxNodeInfo(_doc, _block, _binaryExpression, _stmt, _identifier);
            }
            return nodeInfo;








        }
        #endregion
    }
}
